package com.yeskery.nut.http;

import com.yeskery.nut.application.NutApplication;
import com.yeskery.nut.core.*;
import com.yeskery.nut.util.StringUtils;

import java.io.InputStream;
import java.io.Serializable;
import java.time.ZonedDateTime;
import java.time.format.DateTimeFormatter;
import java.util.LinkedList;
import java.util.List;
import java.util.Locale;

/**
 * 该类是 {@link Response} 的抽象实现，实现自定义 {@link Response} 时，可继承本类
 * @author sprout
 * @version 1.0
 * 2019-03-15 21:52
 */
public abstract class BaseResponse extends BaseNutResponse implements Response, EmitterResponse, Serializable, Cloneable {

	/** 默认的响应日期解析器 */
    private static final DateTimeFormatter DATE_TIME_FORMATTER = DateTimeFormatter.ofPattern("E, dd MMM yyyy HH:mm:ss z", Locale.US);

    /** 默认的响应码 */
    private ResponseCode responseCode = ResponseCode.OK;

    /** 默认响应的协议 */
    private String protocol = "HTTP/1.1";

    /** 默认的字符集 */
    private String charset = "utf-8";

    /** 响应的响应头 */
    private final List<NameAndValue> headers = new LinkedList<>();

    /** 响应的 Cookie */
    private final List<Cookie> cookies = new LinkedList<>();

    /** 是否已经响应 */
    protected volatile boolean output = false;

	/**
	 * 构建一个 {@link BaseRequest}
     * @param nutApplication nut应用对象
	 */
	protected BaseResponse(NutApplication nutApplication) {
        super(nutApplication);
        headers.add(new NameAndValue(HttpHeader.SERVER, "Sprout Nut Server/" + Version.VERSION));
        headers.add(new NameAndValue(HttpHeader.DATE, DATE_TIME_FORMATTER.format(ZonedDateTime.now())));
    }

    @Override
    public void setCode(int code) {
        ResponseCode responseCode = ResponseCode.valueOf(code);
        if (responseCode != null) {
            this.responseCode = responseCode;
        }
    }

    @Override
    public void setCharSet(String charSet) {
        this.charset = charSet;
    }

    @Override
    public void addHeader(NameAndValue nameAndValue) {
        headers.add(nameAndValue);
    }

	@Override
	public void addCookie(Cookie cookie) {
		this.cookies.add(cookie);
	}

    @Override
    public void writeEmpty() {
        writeToResponse(new byte[0]);
    }

    @Override
    public void write(InputStream inputStream) {
        write(inputStream, MediaType.APPLICATION_OCTET_STREAM);
    }

    @Override
    public void write(InputStream inputStream, MediaType mediaType) {
        write(inputStream, mediaType.getValue());
    }

    @Override
    public void write(InputStream inputStream, String mediaType) {
        if (!StringUtils.isEmpty(mediaType)) {
            addHeader(HttpHeader.CONTENT_TYPE, mediaType);
        }
        writeToResponse(inputStream);
    }

    @Override
    public void write(byte[] bytes) {
        write(bytes, MediaType.APPLICATION_OCTET_STREAM);
	}

	@Override
	public void write(byte[] bytes, MediaType mediaType) {
        write(bytes, mediaType.getValue());
	}

    @Override
    public void write(byte[] bytes, String mediaType) {
        if (!StringUtils.isEmpty(mediaType)) {
            addHeader(HttpHeader.CONTENT_TYPE, mediaType);
        }
        writeToResponse(bytes);
    }

	@Override
    public void write(String content) {
        write(content, MediaType.TEXT_PLAIN);
    }

    @Override
    public void writeHtml(String content) {
        write(content, MediaType.TEXT_HTML);
    }

    @Override
    public void writeXml(String content) {
        write(content, MediaType.TEXT_XML);
    }

    @Override
    public void writeJson(String content) {
        write(content, MediaType.APPLICATION_JSON);
    }

    @Override
    public void write(String content, MediaType mediaType) {
        addHeader(HttpHeader.CONTENT_TYPE, mediaType.getValue() + "; charset=" + charset);
		writeToResponse(content.getBytes());
    }

    @Override
    public boolean isResponse() {
        return output;
    }

    @Override
    public BaseResponse clone() {
        try {
            return (BaseResponse) super.clone();
        } catch (CloneNotSupportedException e) {
            throw new NutException(e);
        }
    }

    @Override
    public void sendHeaders(MediaType mediaType) {
        if (mediaType != null) {
            addHeader(HttpHeader.CONTENT_TYPE, mediaType.getValue() + "; charset=" + charset);
        }
        writeHeaderBytes(getResponseHeaders().getBytes());
    }

    @Override
    public void sendBodyBytes(byte[] bytes) {
        writeBodyBytes(bytes);
    }

    @Override
    public void sendBodyInputStream(InputStream inputStream) {
        writeBodyInputStream(inputStream);
    }

    @Override
    public void sendCompleted() {
        output = true;
    }

    /**
     * 向响应中写入响应头数据
     * @param headers 响应头
     */
    protected abstract void writeHeaderBytes(byte[] headers);

    /**
     * 向响应体中写入数据
     * @param bytes 响应体
     */
    protected abstract void writeBodyBytes(byte[] bytes);

    /**
     * 向响应体中写入数据
     * @param inputStream 响应体流
     */
    protected abstract void writeBodyInputStream(InputStream inputStream);

    /**
	 * 向响应中写入数据
	 * @param headers 响应头
	 * @param bytes 响应体
	 */
    protected abstract void writeBytes(byte[] headers, byte[] bytes);

    /**
     * 向响应中写入数据
     * @param headers 响应头
     * @param inputStream 输入流
     */
    protected abstract void writeInputStream(byte[] headers, InputStream inputStream);

    /** 获取响应头 */
    private String getResponseHeaders() {
        for (Cookie cookie : cookies) {
            headers.add(new NameAndValue(HttpHeader.SET_COOKIE, cookie.getCookiePair()));
        }
        StringBuilder headers = new StringBuilder(protocol);
        headers.append(" ").append(responseCode.getCode());
        headers.append(" ").append(responseCode.getDescription());
        headers.append("\r\n");
        this.headers.forEach(header
                -> headers.append(header.getKey()).append(":").append(header.getValue()).append("\r\n"));
        headers.append("\r\n");
        return headers.toString();
    }

    /** 向响应写入数据 */
    private void writeToResponse(byte[] bytes) {
        if (isResponse()) {
            throw new NutException("Response Already Output.");
        }
		addHeader(HttpHeader.CONTENT_LENGTH, String.valueOf(bytes.length));
		String headers = getResponseHeaders();
		writeBytes(headers.getBytes(), bytes);
	}

    /** 向响应写入数据(输入流) */
    private void writeToResponse(InputStream inputStream) {
        if (isResponse()) {
            throw new NutException("Response Already Output.");
        }
        String headers = getResponseHeaders();
        writeInputStream(headers.getBytes(), inputStream);
    }

    /**
     * 设置默认协议版本
     * @param protocol 默认协议版本
     */
    protected void setProtocol(String protocol) {
        this.protocol = protocol;
    }

    /**
     * 设置默认字符集
     * @param charset 默认字符集
     */
    protected void setCharset(String charset) {
        this.charset = charset;
    }
}
